fix(api): make WatchSandbox loss-aware and resumable - #3209
Conversation
99d95d9 to
1f35df9
Compare
BlockedGator is blocked because PR #3209 currently has merge conflicts with Next action: @letv1nnn, please rebase or merge the latest Gator metadata
|
johntmyers
left a comment
There was a problem hiding this comment.
gator-agent
PR Review Status
The merge-conflict blocker is cleared, and the initial code review found two cursor-resume paths that can silently omit events.
Action required: @letv1nnn, please make cursor-space resets unambiguous and make initial cross-source replay safe for reconnect watermarks, including the deterministic regression cases in the inline findings.
Blocking findings:
GATOR-289481f5-01: stale cursors can be accepted in a replacement cursor spaceGATOR-289481f5-02: initial replay can acknowledge an undelivered lower cursor
Carried findings:
- None
Gator metadata
- Validation: Project-valid implementation of linked issue #3055
- Docs: Fern docs updated in
docs/observability/accessing-logs.mdx; the cursor-space claim needs to remain aligned with the fix - Checks: DCO passes; required branch checks have not been dispatched for this current head
- E2E:
test:e2erequired for gateway, sandbox-watch, and SDK reconnect behavior; deferred until blocking review feedback is resolved - Head SHA:
289481f5aa5f1cbd856045f052968017bcb2aeff - Base SHA:
bcf96e4900004e07253d72332a6619f7e2c4e7d7 - Merge base SHA:
bcf96e4900004e07253d72332a6619f7e2c4e7d7 - Patch ID:
b7682c23e96f9512168ee19074a25ae4063f4e72 - Gator payload:
8 - Review mode:
initial - Previous reviewed SHA: none
- Review budget exhausted: no
- Maintainer decision required: no
- Next state:
gator:in-review
PR Review StatusThanks @letv1nnn. The new opaque, epoch-bound cursor fixes stale cursors being accepted after a completed reset, and the merged replay fixes the lower-cursor omission across initial tail sources. I resolved One carried obligation remains: cursor-space validation and replay collection happen under separate locks. If teardown retires the validated space and a publisher creates a replacement space before the tail reads, the old sequence can be applied to the replacement buffers and silently omit their lower events. Action required: make epoch validation and both replay snapshots atomic with respect to cursor-space replacement, or revalidate the epoch after collecting both tails and before emitting any replay event; add a deterministic teardown/republish race regression. Blocking findings:
Carried findings:
Gator metadata
|
|
Label |
|
/ok to test 5cca5aa |
PR Review StatusThanks @letv1nnn. I checked the new post-replay epoch validation against teardown, replacement-space publication, both replay snapshots, and the no-await producer path. It closes the carried cursor-space race, and the independent follow-up review found no new blocking defects. Blocking findings:
Carried findings:
Gator metadata
|
|
@letv1nnn please address open comments so we can get this merge ready |
Add tail_after() to the log and platform event buses, returning every buffered event newer than a client's resume cursor. Each PerSandbox now tracks last_trimmed_seq (the highest seq it has evicted) so a resume is reported as an unrecoverable ResumeGap only when this bus dropped an event the client still needs. Judging gaps by evictions, not by the tail's oldest seq, is required under the shared cursor space: each bus's tail is non-contiguous in the global sequence because the other bus owns the missing seqs, so comparing against tail.front() would flag false gaps. Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
… buses Wire resume_after_cursor into the watch producer. On a non-zero cursor, replay events strictly after it from both the log and platform buses, merge by shared cursor, and emit in order before entering the live loop. A trimmed range on either bus is an unrecoverable gap and terminates the stream with OUT_OF_RANGE carrying the requested and earliest-available cursors, distinct from recoverable lag which warns and continues. Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
Add handler-level tests for the resumable watch stream: replay strictly after the client cursor, merge log and platform events in shared-cursor order, suppress duplicates when resuming at the latest cursor, and terminate with OUT_OF_RANGE when the requested cursor has been trimmed. Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
… cursor Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
…it the watch lag warning before its batch Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
…nitialization Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
Signed-off-by: Artem Lytvyn <alytvyn@redhat.com>
Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
|
/ok to test 2cd5f15 |
johntmyers
left a comment
There was a problem hiding this comment.
gator-agent
PR Review Status
The latest rebase reconciliation preserves the reviewed watch-resume work, but it moves main's bounded relay cleanup from interactive exec to the non-interactive path. An interactive command can now finish while its RPC waits indefinitely for a relay peer that remains open, preventing the terminal event from reaching the client.
Action required: restore bounded proxy cleanup on every interactive-exec completion path, while retaining it on non-interactive exec only if that path also requires the bound.
Blocking findings:
GATOR-2cd5f15b-01: interactive relay cleanup can wait forever after command completion
Carried findings:
- None;
GATOR-289481f5-01,GATOR-289481f5-02, andGATOR-b17b5f39-01remain resolved
Gator metadata
- Validation: Project-valid partial implementation of issue #3055
- Docs: Fern, architecture, and protobuf docs cover the resumable cursor contract
- Checks: DCO passes; current-head Branch Checks and Helm Lint are queued or running
- E2E:
test:e2eis applied;/ok to test 2cd5f15b9411b4871ad92f44b0929d1accfc6241created the mirror; Branch E2E is queued or running - Head SHA:
2cd5f15b9411b4871ad92f44b0929d1accfc6241 - Base SHA:
718dba34304421af9d5e3ac4f9fea148ba08e0fb - Merge base SHA:
718dba34304421af9d5e3ac4f9fea148ba08e0fb - Patch ID:
422d8aeef8f43a30dde125208ca155dd267d01df - Gator payload:
9 - Review mode:
critical_only - Previous reviewed SHA:
55480b1df47aaf5d69bdc9c0c14273fcfa8f512f - Review budget exhausted: yes
- Maintainer decision required: no
- Next state:
gator:in-review
Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
|
/ok to test d49163e |
johntmyers
left a comment
There was a problem hiding this comment.
gator-agent
PR Review Status
The latest delta restores bounded relay cleanup on the interactive exec timeout, error, and normal-completion paths, resolving GATOR-2cd5f15b-01. The bounded critical-only review found no newly introduced Critical defect, so no blocking findings remain.
Blocking findings:
- No blocking findings remain
Carried findings:
- None;
GATOR-289481f5-01,GATOR-289481f5-02,GATOR-b17b5f39-01, andGATOR-2cd5f15b-01remain resolved
Gator metadata
- Validation: Project-valid partial implementation of issue #3055
- Docs: Fern, architecture, and protobuf docs cover the resumable cursor contract
- Checks: DCO, Branch Checks, and Helm Lint pass; current-head E2E is running
- E2E:
test:e2eis applied;/ok to test d49163e0501e4d543f14741ccb15647d7d63f102created the mirror and Branch E2E is running - Head SHA:
d49163e0501e4d543f14741ccb15647d7d63f102 - Base SHA:
718dba34304421af9d5e3ac4f9fea148ba08e0fb - Merge base SHA:
718dba34304421af9d5e3ac4f9fea148ba08e0fb - Patch ID:
bec9bb534dc7b657ef895f7b9aaca40e661af53e - Gator payload:
9 - Review mode:
critical_only - Previous reviewed SHA:
2cd5f15b9411b4871ad92f44b0929d1accfc6241 - Review budget exhausted: yes
- Maintainer decision required: no
- Next state:
gator:watch-pipeline
Monitoring CompleteMonitoring is complete because this PR has merged. Final status: The last Gator state was I removed the active |
Summary
Make
WatchSandboxloss-aware and resumable. Recoverable broadcast lag now emits aSandboxStreamWarningand keeps streaming instead of terminating withRESOURCE_EXHAUSTED. Every resumable event (log line, platform event) carries a monotonic per-sandboxcursor, and a reconnecting client can passresume_after_cursorto replay only what it missed. Unrecoverable loss (a trimmed cursor) terminates the stream withOUT_OF_RANGEso gaps are never silent.Draft / work in progress, server, proto, docs, and the Rust SDK reconnect helper are complete; Go/TS/Python SDK helpers and e2e coverage are still pending (see TODO).
Related Issue
Refs #3055 (partial; issue stays open until SDK helpers land).
Changes
SandboxStreamWarningand continue onRecvError::Lagged;Closedstays terminal. Addslag_warning/lag_warning_eventhelpers.TracingLogBusandPlatformEventBusdraw sequence numbers from one per-sandboxSeqAllocator, so the merged stream is ordered in a single cursor space. Each buffered event is stamped at publish time.SandboxStreamEvent.cursorandWatchSandboxRequest.resume_after_cursor.cursor = 0marks non-resumable events (status snapshots, warnings);resume_after_cursor = 0means no cursor resume (tail-limited replay vialog_tail_lines/event_tail).tail_after(cursor)returns events after a cursor or aResumeGapwhen the requested cursor was trimmed. The producer replays both resumable sources after the cursor, merged in cursor order, then enters live delivery. A gap terminates withOUT_OF_RANGEcarrying the requested and earliest-available cursors.TracingLogBus::removeclears both resumable per-sandbox maps before resetting the shared allocator, closing a publish-during-teardown window.proto,architecture/gateway.md, anddocs/observability/accessing-logs.mdx.OpenShellClient::watch_logs(and the workspace-scoped variant) yield a typedWatchEventstream that tracks the highest cursor, reconnects on transient errors with capped backoff, resendsresume_after_cursorto replay only missed events, forwardsWarningevents, and terminates onOUT_OF_RANGE(newSdkError::OutOfRange). Backoff resets on each delivered event.TODO (follow-up)
snapshot_afterreads both buses under one lock instead of two independenttail_aftercalls, closing the race window. Also adds a coverage floor so a source with a shallow tail limit (e.g. defaultevent_tail=0) can't be silently skipped while a deeper source's cursor is handed out as safe to resume from.OUT_OF_RANGEsemantics fromcrates/openshell-sdk/src/client.rs.sdk/typescript/src/client.ts.python/openshell/sandbox.py.e2e/rust/tests/sandbox_watch_resume.rscovering reconnect-no-loss, cursor expiry →OUT_OF_RANGE, and thestop_on_terminalERROR regression.A lands first since B/C/D/E all build against its resume contract. B/C/D are independent of each other once A merges.
Testing
cargo test -p openshell-server— all pass, including resume replay, cross-source merge order, duplicate suppression, gap →OUT_OF_RANGE, init-race single-delivery, and lag-warning regression.mise run pre-commitChecklist